﻿using MudEngine.Parsing.AST;
using MudEngine.Runtime;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;

namespace MudEngine.Parsing
{
	/// <summary>
	/// The Compiler context class holding all the compile time information
	/// </summary>
	public class CodeGenContext
	{
        /// <summary>
        /// 
        /// </summary>
        private static byte[] _progId = Encoding.UTF8.GetBytes("AC");
        /// <summary>
        /// 
        /// </summary>
        private const int INIT_HASH_TABLE = 100;
        /// <summary>
        /// 
        /// </summary>
        private const int INIT_TEMPVAR_LIST = 10;
        /// <summary>
        /// 
        /// </summary>
        private const int INIT_VAR_LIST = 10;
        /// <summary>
		/// Maximum size of a program
		/// </summary>
		private const int MAX_PROGSIZE = 65536;
        /// <summary>
        /// 
        /// </summary>
        private FileInfo _currentFile = null;
        /// <summary>
        /// Holds all the binary program codes
        /// </summary>
        private byte[] _programCode = new byte[MAX_PROGSIZE];
        /// <summary>
        /// 
        /// </summary>
        /// <param name="sourceFile"></param>
        public CodeGenContext(FileInfo sourceFile)
        {
            this._currentFile = sourceFile;
            this._programCode[0] = _progId[0];
            this._programCode[1] = _progId[1];
            this.Emit4(Program._STRUCT_VER.Major);
            this.Emit4(Program._STRUCT_VER.Minor);
            this.Emit4(Program._STRUCT_VER.Build);
            this.Emit4(Program._STRUCT_VER.Revision);
        }
        /// <summary>
        /// Returns the binary representation of the program being created
        /// </summary>
        /// <returns></returns>
        public byte[] GetProgramCode()
        {
            byte[] result = new byte[this.progCount];
            Array.Copy(this._programCode, result, this.progCount);
            return result;
        }
        /// <summary>
        /// 
        /// </summary>
        private Dictionary<string, Identifier> _identifiers = new Dictionary<string, Identifier>(INIT_HASH_TABLE);
        /// <summary>
        /// 
        /// </summary>
        private List<Variable> _tempVariables = new List<Variable>(INIT_TEMPVAR_LIST);
        /// <summary>
        /// 
        /// </summary>
        private List<Variable> _definedVariables = new List<Variable>(INIT_VAR_LIST);
        /// <summary>
        /// The string table
        /// </summary>
        private StringCollection _strings = new StringCollection();
        /// <summary>
        /// All inherited objects
        /// </summary>
        private List<Inherit> _inherits = new List<Inherit>();
        /// <summary>
        /// Returns the String table
        /// </summary>
        /// <returns></returns>
        public StringCollection Strings()
        {
            return this._strings;
        }
        /// <summary>
        /// Returns the index of the given string in the string table and adds it to the table if it doesn't exist
        /// </summary>
        /// <param name="value"></param>
        /// <returns>The index in the string table</returns>
        internal int AddString(string value)
        {
            int idx = this._strings.IndexOf(value);
            if (idx == -1)
            {
                idx = this._strings.Count;
                this._strings.Add(value);
            }
            return idx;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        private Identifier GetIdentifier(string name)
        {
            if (_identifiers.ContainsKey(name))
                return _identifiers[name];
            return null;
        }
        /// <summary>
        /// Creates a new function with the given name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        private Function AddFunction(string name)
        {
            Function fun = new Function();
            fun.Name = name;
            fun.Index = this._functions.Count;
            this._functions.Add(fun);

            this._identifiers.Add(name, fun);

            return fun;
        }
        /// <summary>
        /// Define a new function
        /// </summary>
        /// <param name="name">Name of the function</param>
        /// <param name="numArgs">Number of parameters the function has</param>
        /// <param name="numLocal">Number of local variables the function defines</param>
        /// <param name="type">Return type of the function</param>
        /// <param name="flags"></param>
        /// <returns>Index of function in the list</returns>
        public Function DefineNewFunction(string name, int numArgs, int numLocal, AType type, FunctionFlags flags)
        {
            Function funp = null;
            Identifier ident = this.GetIdentifier(name);
            if (ident != null)
            {
                if (ident is Variable)
                {
                    // error about the fact that there is already a variable declared with that name
                    return null;
                }

                funp = ident as Function;
                // already defined. Now what?
                if (!(funp.Flags.HasFlag(FunctionFlags.Prototype) || funp.Flags.HasFlag(FunctionFlags.Inherited)) && !flags.HasFlag(FunctionFlags.Prototype))
                {
                    // Error. Redeclarion of function
                    return null;
                }

                if (flags.HasFlag(FunctionFlags.Prototype) && !funp.Flags.HasFlag(FunctionFlags.Prototype))
                    return null;
            }

            if (funp == null)
                funp = this.AddFunction(name);

            funp.Flags = flags;
            funp.NumArguments = numArgs;
            funp.NumLocals = numLocal;
            funp.Address = 0;

            return funp;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public Variable DefineVariable(string name, AType type)
        {
            Variable var = null;

            if (!_identifiers.ContainsKey(name))
            {
                var = new Variable(name, type, _tempVariables.Count, Scope.Global);
                _tempVariables.Add(var);

                _identifiers.Add(name, var);
            }
            else
            {
                // warn about redeclaration or something?
                // what about functions having the same name as variables?

                var = new Variable(name, type, _tempVariables.Count, Scope.Global);
                _tempVariables.Add(var);

            }
            return var;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public Variable DefineNewVariable(string name, AType type)
        {
            Variable var = DefineVariable(name, type);

            this._definedVariables.Add(var);

            return var;
        }
        /// <summary>
        /// Returns the list of Global variables defined in this program
        /// </summary>
        /// <returns></returns>
        public List<Variable> Variables()
        {
            return this._definedVariables;
        }
        /// <summary>
        /// Returns total number of defined variables by this object including the inherited ones
        /// </summary>
        public int TotalNumberOfVariables
        {
            get
            {
                return this._tempVariables.Count;
            }
        }
        /// <summary>
        /// Find the first Identifier defined with the given name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public Identifier FindIdentifier(string name)
        {
            if (_identifiers.ContainsKey(name))
                return _identifiers[name];
            return null;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public Function FindFunction(string name)
        {
            return this.FindIdentifier(name) as Function;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="ident"></param>
        /// <returns></returns>
        public void AddIdentifier(string name, Identifier ident)
        {
            if (!_identifiers.ContainsKey(name))
                _identifiers.Add(name, ident);
            else
                _identifiers[name] = ident;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="fun"></param>
        /// <param name="name"></param>
        /// <param name="aType"></param>
        /// <param name="idx"></param>
        /// <param name="scope"></param>
        public Variable AddLocalVariable(Function fun, string name, AType aType, int idx, Scope scope)
        {
            Variable var = null;
            if (idx == -1)
                var = fun.AddVariable(name, aType);
            else
            {
                fun.Locals[idx] = new Variable(name, aType, idx, scope);
                var = fun.Locals[idx];
            }
            this.AddIdentifier(name, var);

            return var;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public List<Inherit> Inherits()
        {
            return this._inherits;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="inherit"></param>
        /// <returns></returns>
        public Inherit FindInherit(string inherit)
        {
            return this._inherits.FirstOrDefault(i => i.Filename.Equals(inherit));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="inherit"></param>
        /// <param name="program"></param>
        public Inherit DefineInherit(string inherit, Program program)
        {
            Inherit inh = this.FindInherit(inherit);
            if (inh != null)
                this.AddWarningFormat("Cannot inherit the file '{0}' twice", inherit);
            else
            {
                inh = new Inherit();
                inh.Filename = inherit;
                inh.Program = program;
                inh.Index = this._inherits.Count;
                inh.VariableIndexOffset = this._tempVariables.Count;

                if (this._inherits.Count > 0)
                {
                    Inherit prevInherit = this._inherits[this._inherits.Count - 1];
                    inh.FunctionIndexOffset = prevInherit.FunctionIndexOffset + prevInherit.Program.FunctionTable.Count;
                }
                else
                    inh.FunctionIndexOffset = 0;

                this._inherits.Add(inh);

                if (program != null)
                {
                    // Copy all defined variables from the inherited program to this program
                    this.CopyVariables(program);

                    // Get reference to ___INIT function and store it as initializer function in the inherit object
                    int nf = program.FunctionTable.Count;
                    if (nf > 0 && program.FunctionTable[nf - 1].Name.Equals(Function.INIT_FUNC))
                        inh.Initalizer = program.FunctionTable[--nf];

                    for (int i = 0; i < nf; i++)
                    {
                        Program prog = program;
                        int index = i;

                        Function funp;

                        int low, mid, high;
                        // go up the inheritance tree and pick the real implementation of this function
                        while (prog.FunctionTable[index].Flags.HasFlag(FunctionFlags.Inherited))
                        {
                            low = 0;
                            high = prog.Inherits.Count - 1;

                            while (high > low)
                            {
                                mid = (low + high + 1) >> 1;
                                if (prog.Inherits[mid].FunctionIndexOffset > index)
                                    high = mid - 1;
                                else low = mid;
                            }
                            index -= prog.Inherits[low].FunctionIndexOffset;
                            prog = prog.Inherits[low].Program;
                        }

                        //index -= prog->last_inherited;
                        funp = prog.FunctionTable[index];

                        if (!_identifiers.ContainsKey(funp.Name))
                        {
                            // copy_new_function(from, i, prog, index, typemod);
                            Function _function = this.AddFunction(funp.Name);
                            _function.NumArguments = funp.NumArguments;
                            _function.NumLocals = funp.NumLocals;
                            _function.Type = funp.Type;
                            _function.Address = -1;
                            _function.Flags |= FunctionFlags.Inherited;
                        }
                        else
                        {
                            // overload_function(from, i, prog, index, num, typemod);
                        }
                    }
                }
            }
            return inh;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="alias"></param>
        /// <param name="functionName"></param>
        /// <returns></returns>
        public Function FindInheritedFunction(string alias, string functionName)
        {
            foreach (Inherit inherit in this._inherits)
            {
                if (inherit.Program.Filename.EndsWith(string.Format("/{0}.c", alias)))
                {
                    Function function = inherit.Program.FindFunction(functionName);
                    return function;
                }
            }
            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// 
        /// <param name="from"></param>
        private void CopyVariables(Program from)
        {
            foreach (Inherit inherit in from.Inherits)
                this.CopyVariables(inherit.Program);

            foreach (Variable variable in from.Variables)
                this.DefineVariable(variable.Name, variable.Type);
        }
		/// <summary>
		/// All functions in the class
		/// </summary>
		private List<Function> _functions = new List<Function>();
		/// <summary>
		/// Returns the list of functions defined in this program
		/// </summary>
		/// <returns></returns>
		public List<Function> Functions()
		{
			return this._functions;
		}
		/// <summary>
		/// Holds the current function being coded
		/// </summary>
		public Function CurrentFunction { get; set; }
		/// <summary>
		/// Holds the current variable being coded
		/// </summary>
		public Variable CurrentVariable { get; set; }

		#region Binary emit functions
		/// <summary>
		/// holds the current program counter
        /// first 2 bytes hold the application id for future binary format support
		/// </summary>
		private int progCount = 2;
		/// <summary>
		/// Current program counter
		/// </summary>
		public int PC
		{
			get
			{
				return this.progCount;
			}
		}
		/// <summary>
		/// Puts a byte in the binary code
		/// </summary>
		/// <param name="val"></param>
		public void Emit1(byte val)
		{
			_programCode[progCount++] = val;
		}
		/// <summary>
		/// Puts a short in the binary code
		/// </summary>
		/// <param name="val"></param>
		public void Emit2(short val)
		{
			_programCode[progCount++] = (byte)(val >> 8);
			_programCode[progCount++] = (byte)val;
		}
		/// <summary>
		/// Puts an int in the binary code
		/// </summary>
		/// <param name="val"></param>
		public void Emit4(int val)
		{
			_programCode[progCount++] = (byte)(val >> 24);
			_programCode[progCount++] = (byte)(val >> 16);
			_programCode[progCount++] = (byte)(val >> 8);
			_programCode[progCount++] = (byte)val;
		}
		/// <summary>
		/// Puts a long in the binary code
		/// </summary>
		/// <param name="val"></param>
		public void Emit8(long val)
		{
			_programCode[progCount++] = (byte)(val >> 56);
			_programCode[progCount++] = (byte)(val >> 48);
			_programCode[progCount++] = (byte)(val >> 40);
			_programCode[progCount++] = (byte)(val >> 32);
			_programCode[progCount++] = (byte)(val >> 24);
			_programCode[progCount++] = (byte)(val >> 16);
			_programCode[progCount++] = (byte)(val >> 8);
			_programCode[progCount++] = (byte)val;
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="val"></param>
		public void Emit8(double val)
		{
			throw new NotImplementedException();
		}
		/// <summary>
		/// Emits the binary code for an opcode with a given 4-byte value
		/// </summary>
		/// <param name="opCode"></param>
		/// <param name="val"></param>
		/// <returns>Returns the program counter position of the param 'val'</returns>
		public int EmitOpCode(OpCodes opCode, int val)
		{
			this.Emit1((byte)opCode);
			this.Emit4(val);
			return this.progCount - 4;
		}
        /// <summary>
        /// 
        /// </summary>
        /// <param name="opCode"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public int EmitOpCodeS(OpCodes opCode, short val)
        {
            this.Emit1((byte)opCode);
            this.Emit2(val);
            return this.progCount - 2;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="opCode"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public int EmitOpCodeB(OpCodes opCode, byte val)
        {
            this.Emit1((byte)opCode);
            this.Emit1(val);
            return this.progCount - 1;
        }
        /// <summary>
		/// Emits a single opcode
		/// </summary>
		/// <param name="opCode"></param>
		public void EmitOpCode(OpCodes opCode)
		{
			this.Emit1((byte)opCode);
		}
		/// <summary>
		/// Patches a value at a given address and replaces it with another value
		/// </summary>
		/// <param name="adr"></param>
		/// <param name="val"></param>
		public void PatchCode(int adr, int val)
		{
			_programCode[adr] = (byte)(val >> 24);
			_programCode[adr + 1] = (byte)(val >> 16);
			_programCode[adr + 2] = (byte)(val >> 8);
			_programCode[adr + 3] = (byte)val;
		}

		#endregion
		/// <summary>
		/// Updates the function with the current program counter position
		/// </summary>
		/// <param name="function"></param>
		public void UpdateFunctionAddress(Function function)
		{
			function.Address = this.progCount;
		}
		/// <summary>
		/// Holds all the messages
		/// </summary>
		private StringCollection _errors = new StringCollection();
		/// <summary>
		/// Adds an error message to the list of compiler errors
		/// </summary>
		/// <param name="msg"></param>
		public void AddError(string msg)
		{
			this._errors.Add(msg);
			Console.WriteLine(msg);
		}
		/// <summary>
		/// Adds a formatted error to the list of compiler errors
		/// </summary>
		/// <param name="msg"></param>
		/// <param name="args"></param>
		public void AddErrorFormat(string msg, params object[] args)
		{
			string errorMessage = string.Format(msg, args);
			this._errors.Add(errorMessage);
			Console.WriteLine(errorMessage);
		}
		/// <summary>
		/// Adds a formatted warning to the list of compiler warning messages
		/// </summary>
		/// <param name="msg"></param>
		/// <param name="args"></param>
		public void AddWarningFormat(string msg, params object[] args)
		{
			string errorMessage = string.Format("WARNING: " + msg, args);
			this._errors.Add(errorMessage);
			Console.WriteLine(errorMessage);
		}
		/// <summary>
		/// All the errors found in the program
		/// </summary>
		public StringCollection Errors
		{
			get
			{
				return this._errors;
			}
		}
    }
}
